let not_function = false;;
let is_function = true;;
let not_init = false;;
let is_init = true;;

let rec fake_dec d r fun_flag = match d with
		Empty -> r
	| Const(t, x, v) -> bind r x (DConst (t, v))
	| Var (t, x) -> bind r x (DVar (t, 0))
	| Init (t, x, e) -> bind r x (DVar (t, 0))
	| Array (t, x, v) -> bind r x ( DArray (t, v, 0) )
	| Function (in_t,name,tcall, out_t,param_name,body,ret) ->
				if fun_flag 
						then raise(BadFunctionDeclaration("You can't declare a function into a function", name)) 
						else bind r name (DFunc(tcall,in_t,out_t,param_name,body,ret))
	| Dseq(d1, d2) -> fake_dec d2 (fake_dec d1 r fun_flag) fun_flag
	;;

let rec type_exp e r init_flag = match e with
		N n -> DInt
	| B b -> DBool
	| Fun f ->
		if (init_flag)
		then raise (BadInitialization("You can not initialize a variable with the evaluation of a function"))
		else 
		(match f with
				| CBV(name, p_value) ->
						(match applyenv r name with
								DFunc(tcall,in_t, out_t, p_name, Program(d, block), Return (e)) -> out_t
							| _ -> raise (TypeMismatch ("You are using a function as something else")))
				| CBR(name, ref) ->
						(match applyenv r name with
								DFunc(tcall,in_t, out_t, p_name, Program(d, block), Return (e)) -> out_t
							| _ -> raise (TypeMismatch ("You are using a function as something else")))
			)
	| Val x ->
			(match applyenv r x with
					DVar (t, l) -> t
				| DConst (t, c) -> t
				| DArray (_, _, l) -> raise (TypeMismatch (
									"You have tried to access array" ^ x ^ " as a variable"))
				| DFunc(_,_, _, _, _, _) -> raise (TypeMismatch ( "You have tried to call function" ^ x ^ " as a variable"))
				| Unbound -> raise (UnboundIde x)
			)
	| ArrayVal (x, e') -> (match applyenv r x with
				| DArray (t, dim, l) -> t
				| DVar (_, l) -> raise (TypeMismatch ("You have tried to access variable " ^ x ^ " as an array"))
				| DConst (t, c) -> raise (TypeMismatch (
									"You have tried to access constant " ^ x ^ " as an array"))
				| DFunc(_,_, _, _, _, _) -> raise (TypeMismatch ( "You have tried to call function" ^ x ^ " as an array"))
				| Unbound -> raise (UnboundIde x)
			)(*1175*)
	| EAdd (e1, e2) | ESub(e1, e2) | EMul (e1, e2) | EMax (e1, e2) ->
			(
				match ( ((type_exp e1 r init_flag) = DInt), ((type_exp e2 r init_flag) = DInt) ) with
				| (true, true) -> DInt
				| (false, true) -> raise(TypeMismatch ("The left operand is not an Int"))
				| (true, false) -> raise(TypeMismatch ("The right operand is not an Int"))
				| (false, false) -> raise(TypeMismatch ("Both the operands are not an Int"))
			)
	| Eq (e1, e2) | Lt(e1, e2) ->
			(
				match ( ((type_exp e1 r init_flag) = DInt), ((type_exp e2 r init_flag) = DInt) ) with
				| (true, true) -> DBool
				| (false, true) -> raise(TypeMismatch ("The left operand is not an Int"))
				| (true, false) -> raise(TypeMismatch ("The right operand is not an Int"))
				| (false, false) -> raise(TypeMismatch ("Both the operands are not an Int"))
			)(*1938*)
	| Not e' -> if ( (type_exp e' r  init_flag)= DBool ) then DBool
			else raise(TypeMismatch ("-Not- argument must be Bool"))
	
	| And (e1, e2) | Or(e1, e2) ->
			(
				match ( ((type_exp e1 r init_flag) = DBool), ((type_exp e2 r init_flag) = DBool) ) with
				| (true, true) -> DBool
				| (false, true) -> raise(TypeMismatch ("The left operand is not a Bool"))
				| (true, false) -> raise(TypeMismatch ("The right operand is not a Bool"))
				| (false, false) -> raise(TypeMismatch ("Both the operands are not a Bool"))
			)
and
check_com c rho = match c with
		Skip -> false
	| AssignVar(x, e) -> (match applyenv rho x with
					DVar (t, l) -> ((match (t, (type_exp e rho not_init)) with
								| (DInt, DInt) -> false
								| (DBool, DBool) -> false
								| _ -> true
							))
				| _ -> raise (TypeMismatch ("You are trying to assign to non-variable" ^ x)))
	| AssignArray(x, e, e') -> (match (applyenv rho x) with
					DArray (t, dim, l) -> ((match (t, (type_exp e' rho not_init), (type_exp e rho not_init)) with
								| (DInt, DInt, DInt) -> false
								| (DBool, DBool, DInt) -> false
								| _ -> true
							))
				| _ -> raise (TypeMismatch ("You are trying to assign to non-variable" ^ x)))
	| Exchange(x, e1, e2) -> (match ( (type_exp e1 rho not_init), (type_exp e2 rho not_init)) with
				| (DInt, DInt) -> false
				| _ -> true )
	| Cseq(c1, c2) ->
			((check_com c1 rho) || (check_com c2 rho))
	| If(e, c1, c2) ->
			if (type_exp e rho not_init) = DBool (*3444*)
			then ((check_com c1 rho) || (check_com c2 rho))
			else true
	| While(e, c') ->
			if (type_exp e rho not_init) = DBool
			then check_com c' rho
			else true

and

dec_check d r fun_flag = match d with
	| Init(t, x, e) -> if (t = (type_exp e r is_init)) then false else true
	| Dseq(d1, d2) -> (	dec_check d1 r fun_flag) || dec_check d2 (fake_dec d1 r not_function) fun_flag
	| Function (in_t, name,tcall, out_t, param_name, Program(dec, b1), Return(e)) ->
			let r' = ((fake_dec (
							Dseq(Function(in_t, name,tcall, out_t, param_name, Program(dec, b1), Return(e)),
								Var(in_t, param_name)) ) r not_function))
			in (
				if(dec_check dec r' is_function) = false
				then let r'' =  (fake_dec dec r' is_function)
					in (out_t <> (type_exp e r'' not_init)) ||	check_block b1 r'' is_function
				else failwith "errori di tipo nelle dichiarazioni hhhhhhh"
			)
	| _ -> false

and

check_block block rho fun_flag = match block with
	| BlockSeq(b1, b2) -> check_block b1 rho fun_flag || check_block b2 rho fun_flag
	| DecBlock (dec, b1) ->
			if(dec_check dec rho fun_flag) = false then
				check_block b1 (fake_dec dec rho fun_flag) fun_flag
			else failwith "errori di tipo nelle dichiarazioni1"
	| Com (com) -> check_com com rho
;;

let check_prog (Program (d, b), rho) =
	if (dec_check d rho not_function ) = false then (check_block b ( (fake_dec d rho not_function)) not_function )
	else failwith "errori di tipo nelle dichiarazioni2";;
